/*
 * Copyright 2010-2012 Susanta Tewari. <freecode4susant@users.sourceforge.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package genomemap.map;

import genomemap.data.AssignmentData;
import genomemap.data.KSA00Data;

import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author Susanta Tewari
 * @version 1.0 12/05/2009
 */
public abstract class ContigMap implements Iterable<ContigMap.Contig> {

    /**
     * the probe map for the contig map. A probe map encapsulates the linkage group and a list
     * of probes.
     */
    private final ProbeMap probeMap;

    /**
     * estimated via the abstract method <code>buildContigMap</code> during instantiation.
     */
    protected List<Contig> contigs;

    /** Field description */
    private AssignmentData assignmentData;

    /**
     * Constructs ...
     *
     *
     * @param probeMap
     */
    protected ContigMap(final ProbeMap probeMap) {

        this.probeMap = probeMap;
        contigs       = Collections.unmodifiableList(buildContigMap());
    }

    /**
     * Method description
     *
     *
     * @param assignmentData
     */
    public void setAssignmentData(final AssignmentData assignmentData) {
        this.assignmentData = assignmentData;
    }

    /**
     * Method description
     *
     *
     * @return
     */
    public KSA00Data getMapData() {
        return probeMap.getMapData();
    }

    /**
     * gets the gap between this contig and the next one.
     *
     * @param contig
     * @return
     */
    public Double contigGap(final Contig contig) {
        return probeMap.getProbeGap(contig.getEndProbe());
    }

    /**
     * initializes cloneMap and partitionMap
     *
     * @return
     */
    protected abstract List<Contig> buildContigMap();

    /**
     * @return
     */
    public List<String> getClones() {

        final List<String> result = new ArrayList<>();

        for (final Contig contig : contigs) {
            result.addAll(contig.getClones());
        }

        return result;
    }

    /**
     * @return
     */
    public Integer getContigCount() {
        return contigs.size();
    }

    /**
     * Average size (in the same units as the clone size) of a contig in this map.
     *
     * @return Average size of a contig in this map.
     */
    public Double getAvgContigSize() {

        double contigSize = 0;

        for (final Contig contig : this) {
            contigSize += contig.getSize();
        }

        return contigSize / getContigCount();
    }

    /**
     * Average number of clones in a contig of this map.
     *
     * @return average number of clones in a contig of this map
     */
    public Double getAvgClonesPerContig() {

        int cloneCount = 0;

        for (final Contig contig : this) {
            cloneCount += contig.getClones().size();
        }

        final double result = ((double) cloneCount) / getContigCount();

        assert result > 1;

        return result;
    }

    /**
     * Method description
     *
     *
     * @return
     */
    public List<String> getProbes() {
        return probeMap.getProbes();
    }

    /**
     * Method description
     *
     *
     * @return
     */
    public Double estimateChromosomeSize() {
        return probeMap.estimateChromosomeSize();
    }

    /**
     * Method description
     *
     *
     * @return
     */
    @Override
    public Iterator<Contig> iterator() {
        return contigs.iterator();
    }

    /**
     * Method description
     *
     *
     * @return
     */
    @Override
    public String toString() {

        final Set<String> partitionMap = new HashSet<>(contigs.size());

        for (final Contig contig : contigs) {
            partitionMap.addAll(contig.getProbes());
        }

        final List<String> probes = probeMap.getProbes();
        final StringBuilder strBuilder = new StringBuilder((partitionMap.size() + probes.size())
                                             + 20);
        String rowSeparator       = "";

        for (int i = 0; i < (partitionMap.size() + probes.size()) + 11; i++) {
            rowSeparator += "-";
        }

        rowSeparator += "\n";

        for (Iterator<Contig> itr = contigs.iterator(); itr.hasNext(); ) {

            final Contig contig = itr.next();

            for (final String clone : contig.getClones()) {

                if (getProbes().contains(clone)) {
                    strBuilder.append("*");
                } else {
                    strBuilder.append(" ");
                }

                strBuilder.append(clone).append("   ");

                for (String probe : probes) {

                    if (partitionMap.contains(probe)) {
                        strBuilder.append("|");
                    }


                    // check if hybridizing
                    if (probeMap.getMapData().isHybridizing(clone, probe)) {
                        strBuilder.append("1");
                    } else {
                        strBuilder.append(".");
                    }
                }


                // start-get genes assigned to this clone
                if (assignmentData != null) {

                    for (String s : assignmentData.getGeneAssignments(clone)) {

                        strBuilder.append(s);

                        if (itr.hasNext()) {
                            strBuilder.append(", ");
                        }
                    }

                } else {

                    Logger.getLogger(getClass().getName()).log(
                        Level.INFO,
                        "assignment data not set. can`t tag clones with their assigned genes");
                }


                // end
                strBuilder.append("\n");
            }

            if (itr.hasNext()) {
                strBuilder.append(rowSeparator);
            }
        }

        return strBuilder.toString();
    }

    /**
     * ----    -----    ----- (probes)
     * ------   ------  (connecting clones)
     * <p>
     * In shotgun DNA sequencing projects, a contig (from contiguous) is a set of overlapping DNA
     * segments derived from a single genetic source. A contig in this sense can be used to deduce the
     * original DNA sequence of the source.
     * <p>
     * A contig map depicts the relative order of a linked library of contigs representing a complete
     * chromosome segment.
     * Staden, R. "A strategy of DNA sequencing employing computer programs", Nucleic Acids Res. 7,
     * 2601-2610, (1979). [1]
     * <a href="http://www.pubmedcentral.nih.gov/articlerender.fcgi?artid=327874">Link</a>
     * Source: <a href="http://en.wikipedia.org/wiki/Contig">Wiki:Contig</a>
     *
     * @author Susanta Tewari
     * @version 1.0 12/03/2009
     */
    public class Contig {

        /**
         * probes in the contig i.e. probes in clones
         */
        private final List<String> probes = new ArrayList<>();

        /**
         * All the contiguous clones (including probes) in the contig
         */
        private final List<String> clones = new ArrayList<>();

        void addProbe(final String probe) {

            clones.add(probe);
            probes.add(probe);
        }

        void addClone(final String clone) {
            clones.add(clone);
        }

        void addClones(final List<String> clones) {
            this.clones.addAll(clones);
        }

        String getStartProbe() {
            return probes.get(0);
        }

        String getEndProbe() {
            return probes.get(probes.size() - 1);
        }

        /**
         * Method description
         *
         *
         * @return
         */
        public Double getSize() {
            return probeMap.estimateContigSize(this);
        }

        /**
         * Returns the clones in the contig. This includes the probes as well.
         *
         * @return clones in the contig
         */
        public List<String> getClones() {
            return clones;
        }

        /**
         * Returns the probes in the contig
         *
         * @return probes in the contig
         */
        List<String> getProbes() {
            return probes;
        }
    }
}
